﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.Hosting;
using System.Web.Security;
using System.Web.UI;
using Jayden.Dll.Core;
using Jayden.Dll.Core.Drawing;
using Jayden.Dll.Core.Logging;
using Jayden.Dll.Core.Translations;
using Jayden.Dll.Web.Enums;
using Jayden.Dll.Web.Events;
using Jayden.Dll.Web.Helper;
using Jayden.Dll.Web.HtmlControls;
using Jayden.Dll.Web.HtmlControls.Generics;
using Jayden.Dll.Web.Json;
using Jayden.Dll.Web.Scripts;
using Jayden.Dll.Web.Styles;
using Yahoo.Yui.Compressor;

namespace Jayden.Dll.Web
{
	public class HttpTestHandler : System.Web.IHttpHandler, System.Web.SessionState.IRequiresSessionState
	{
		public bool IsReusable { get { return true; } }

		public void ProcessRequest(System.Web.HttpContext context)
		{
			context.Response.ContentType = HtmlHelper.MimeType;
			context.Response.Write(HtmlHelper.Html5Doctype);
			context.Response.Write("<html><head><title>Hello, World</title></head><body><h1>Hello, World</h1><p>It's working.</p></body></html>");
		}
	}
	public class HttpHandler : System.Web.IHttpHandler, System.Web.SessionState.IRequiresSessionState
	{
		private bool m_IsReusable = true;
		private Configuration m_Configuration;
		private UriManager m_UriManager;
		private HtmlControlLoader m_ControlLoader;

		public static readonly Logger Log;

		static HttpHandler()
		{
			Log = Logger.GetLogger(typeof(HttpHandler).FullName);
			FileHandler handler = new FileHandler(HostingEnvironment.MapPath("/httpHandler.log"));
			handler.Formatter = new SimpleFormatter();
			Log.AddHandler(handler);
#if DEBUG
			Log.Level = Level.All;
#else
			Log.Level = Level.Warning;
#endif
		}

		public bool IsReusable { get { return m_IsReusable; } }

		public UriManager UriManager
		{
			get
			{
				if (m_UriManager == null)
				{
					lock (this)
					{
						if (m_UriManager == null)
						{
							m_UriManager = new UriManager(Configuration.GetConfiguration("Urls"), null);
						}
					}
				}
				return m_UriManager;
			}
		}

		public Configuration Configuration
		{
			get
			{
				if (m_Configuration == null)
				{
					lock (this)
					{
						if (m_Configuration == null)
							m_Configuration = new Configuration(HostingEnvironment.MapPath("/Configuration.xml"));
					}
				}
				return m_Configuration;
			}
		}

		public HtmlControlLoader ControlLoader
		{
			get
			{
				if (m_ControlLoader == null)
				{
					lock (Configuration)
					{
						if (m_ControlLoader == null)
						{
							Configuration config = Configuration.GetConfiguration("ControlLoader");
							m_ControlLoader = new HtmlControlLoader(config);
						}
					}
				}
				return m_ControlLoader;
			}
		}

		private HashSet<string> m_ForbiddenExtensions;
		public HashSet<string> ForbiddenExtensions
		{
			get
			{
				if (m_ForbiddenExtensions == null)
				{
					lock (this)
					{
						if (m_ForbiddenExtensions == null)
							m_ForbiddenExtensions = new HashSet<string>(Configuration.GetValues("HttpHandler/Forbidden/Extension/@value"));
					}
				}
				return m_ForbiddenExtensions;
			}
		}

		private string m_UnknowExtensionMimeType;
		private Dictionary<string, string> m_MimeTypesByExtensions;
		public Dictionary<string, string> MimeTypesByExtensions
		{
			get
			{
				if (m_MimeTypesByExtensions == null)
				{
					lock (this)
					{
						if (m_MimeTypesByExtensions == null)
							m_MimeTypesByExtensions = Configuration.GetDictionary("HttpHandler/MimeTypes/Extension/@value", "HttpHandler/MimeTypes/Extension/@type");
					}
				}
				return m_MimeTypesByExtensions;
			}
		}
		public string UnknowExtensionMimeType
		{
			get
			{
				if (m_UnknowExtensionMimeType == null)
				{
					lock (this)
					{
						if (m_UnknowExtensionMimeType == null)
							m_UnknowExtensionMimeType = Configuration.GetValue("HttpHandler/MimeTypes/@default", "application/octet-stream");
					}
				}
				return m_UnknowExtensionMimeType;
			}
		}

		private void ResetConfiguration()
		{
			m_Configuration = null;
			m_ForbiddenExtensions = null;
			m_MimeTypesByExtensions = null;
			m_UnknowExtensionMimeType = null;
			m_UriManager = null;
		}

		public void ProcessRequest(System.Web.HttpContext context)
		{
			try
			{
#if DEBUG
				if (context.Request["reset-configuration"] == "true")
					ResetConfiguration();
#endif
				Log.Fine("Request Url from '{1}' : '{0}'", context.Request.Url, context.Request.UserHostAddress);
				if (context.Request.HttpMethod.ToLower() == "post")
					Log.Fine("Request Form : '{0}'", context.Request.Form.ToString());
				Configuration config = Configuration;
				string virtualPath = context.Request.Path;
				string physicalPath = context.Request.PhysicalPath;
				// uri should be used otherwise the mapped uri and query wont be forwarded.
				Jayden.Dll.Web.Uri uri = new Web.Uri(context.Request.Url);
				#region Url Mapping
				UriAccess uriAccess = null;
				Uri mappedUri = UriManager.Manage(uri, out uriAccess);
				if (uriAccess != null && !uriAccess.HaveAccess(null))
				{
					Log.Fine("Access denied for uri '{0}', redirecting ...", uri);
					if (!context.User.Identity.IsAuthenticated)
					{
						Uri loginUri = new Uri(uri, UriManager.NotAuthenticatedUriString, true);
						Uri copy = new Uri(context.Request.Url);
						copy["format"] = null;
						loginUri["returnUrl"] = copy.ToString(true);
						context.Response.Redirect(loginUri.ToString(true), true);
					}
					else
					{
						Uri homeUri = new Uri(uri, "/" + UriManager.DefaultFileName, true);
						context.Response.Redirect(homeUri.ToString(true), true);
					}
				}
				if (mappedUri != null && uri != mappedUri)
				{
					physicalPath = context.Server.MapPath(mappedUri.AbsolutePath);
					// override uri query with those of mapped
					uri.MergeQuery(mappedUri);
					// replace all query of mapped with the merged of uri
					mappedUri.MergeQuery(uri);
					uri = mappedUri;
				}
				#endregion
				if (Directory.Exists(physicalPath))
					physicalPath += "\\" + UriManager.DefaultFileName;
				string baseExtension = Path.GetExtension(physicalPath).ToLowerInvariant();
				if (string.IsNullOrEmpty(baseExtension))
				{
					baseExtension = ".html";
					physicalPath += baseExtension;
				}

				RenderMode renderMode = RenderMode.Production;
#if DEBUG
				renderMode = RenderMode.Debug;
#endif
				string renderModeString = uri["renderMode"];
				if (renderModeString != null)
					Enum.TryParse<RenderMode>(renderModeString, true, out renderMode);

				bool userFolderAccess = true;
				string physicalUserFolder = context.Server.MapPath("/Users/");
				// only allow physical access to user files to user itself and admin
				if (physicalPath.StartsWith(physicalUserFolder) && !physicalPath.StartsWith(physicalUserFolder + context.User.Identity.Name + "\\") && !Roles.IsUserInRole("admin"))
					userFolderAccess = false;

				#region Physical Request
				if (File.Exists(physicalPath) && userFolderAccess && !ForbiddenExtensions.Contains(baseExtension))
				{
					FileInfo fileInfo = new FileInfo(physicalPath);
					string ifModifiedSince = context.Request.Headers["If-Modified-Since"];
					if (!string.IsNullOrWhiteSpace(ifModifiedSince))
					{
						// add 1 second as compare take millisecs in account and the header format support precision to second
						DateTime lastGet = DateTime.MinValue;
						try { lastGet = DateTime.Parse(ifModifiedSince, CultureInfo.InvariantCulture).AddSeconds(1); }
						catch (Exception e) { Log.Throwing(GetType().FullName, "void ProcessRequest(System.Web.HttpContext context)", e); }
						if (fileInfo.LastWriteTime.CompareTo(lastGet) <= 0)
						{
							// if the file is not modified we just send the request back as not modified
							context.Response.Status = "304 Not Modified";
							context.Response.StatusCode = 304;
							context.Response.StatusDescription = "Not Modified";
							context.Response.End();
						}
					}
					Log.Fine("Request Physical File : '{0}'", physicalPath);
					string compressedResponse = null;
					if (baseExtension == ".js")
					{
						context.Response.ContentType = JsHelper.MimeType;
						if (renderMode == RenderMode.Production)
							compressedResponse = JavaScriptCompressor.Compress(File.ReadAllText(physicalPath));
						if (context.Request["degradeJs"] == "true")
							compressedResponse = "/* degradeJs is true no JavaScript file will be available. */";
					}
					else if (baseExtension == ".css")
					{
						context.Response.ContentType = CssHelper.MimeType;
						if (renderMode == RenderMode.Production)
							compressedResponse = CssCompressor.Compress(File.ReadAllText(physicalPath));
						else if (renderMode == RenderMode.Debug)
						{
							// validate css code
							CssFile css = CssHelper.Load(physicalPath);
							int version = css.CheckValidity();
							StringBuilder sb = new StringBuilder();
							using (StringWriter writer = new StringWriter(sb))
							{
								writer.WriteLine("/* Css{2}: Validated for {0} at {1} */", context.Request.Url, DateTime.Now.ToString(CultureInfo.InvariantCulture), version);
								css.Render(writer, renderMode, 0);
							}
							compressedResponse = sb.ToString();
						}
						if (context.Request["degradeCss"] == "true")
							compressedResponse = "/* degradeCss is true no Css file will be available. */";
					}
					else if (baseExtension == ".html")
					{
						context.Response.ContentType = HtmlHelper.MimeType;
						if (renderMode == RenderMode.Production)
						{
							compressedResponse = File.ReadAllText(physicalPath);
							Regex regex = new Regex("\\s+");
							compressedResponse = regex.Replace(compressedResponse, " ");
						}
					}
					else // get mimetype from extension
					{
						string contentType = null;
						if (!MimeTypesByExtensions.TryGetValue(baseExtension, out contentType))
							contentType = UnknowExtensionMimeType;
						context.Response.ContentType = contentType;
					}

					string lastModified = fileInfo.LastWriteTimeUtc.ToString("ddd, d MMM yyy HH:mm:ss", CultureInfo.InvariantCulture) + " GMT";
					// allow full caching for one hour
					int cachingTimeInHour = 30;
#if DEBUG
					cachingTimeInHour = 1;
#endif
					TimeSpan maxAge = new TimeSpan(cachingTimeInHour, 0, 0);
					context.Response.AddHeader("Cache-Control", "public, max-age=" + (int)maxAge.TotalSeconds + ", must-revalidate");
					context.Response.AddHeader("Last-Modified", lastModified);

					if (context.Response.ContentType.StartsWith("image/"))
					{
						string maxWidthStr = context.Request["maxWidth"];
						string maxHeightStr = context.Request["maxHeight"];
						bool greyscale = (context.Request["greyscale"] == "true");
						if (maxHeightStr != null && maxWidthStr != null)
						{
							try
							{
								int maxWidth = int.Parse(maxWidthStr);
								int maxHeight = int.Parse(maxHeightStr);
								using (Image image = Image.FromFile(physicalPath))
								{
									using (Bitmap resized = ImageHelper.Resize(image, maxWidth, maxHeight))
									{
										Bitmap second = null;
										if (greyscale)
											second = ImageHelper.Greyscale(resized);
										using (MemoryStream stream = new MemoryStream())
										{
											(second ?? resized).Save(stream, image.RawFormat);
											stream.WriteTo(context.Response.OutputStream);
											if (second != null)
											{
												second.Dispose();
												second = null;
											}
											return;
										}
									}
								}
							}
							catch (Exception e)
							{
								Log.Throwing(GetType().FullName, "void ProcessRequest(System.Web.HttpContext context)", e);
							}
						}
						else if (greyscale)
						{
							try
							{
								using (Image image = Image.FromFile(physicalPath))
								{
									using (Bitmap resized = ImageHelper.Greyscale(image as Bitmap))
									{
										using (MemoryStream stream = new MemoryStream())
										{
											resized.Save(stream, image.RawFormat);
											stream.WriteTo(context.Response.OutputStream);
											return;
										}
									}
								}
							}
							catch (Exception e)
							{
								Log.Throwing(GetType().FullName, "void ProcessRequest(System.Web.HttpContext context)", e);
							}
						}
						else if (context.Request["text"] != null && baseExtension == ".png")
						{
							Font font = new Font("Times New Roman", 16, FontStyle.Regular, GraphicsUnit.Pixel);
							Color background = Color.Empty;
							Brush brush = new SolidBrush(Color.DarkBlue);
							string text = context.Request["text"];
							using(Image textImage= ImageHelper.GetString(text, font, brush, background)) {
								using (MemoryStream stream = new MemoryStream())
								{
									textImage.Save(stream, ImageFormat.Png);
									stream.WriteTo(context.Response.OutputStream);
									return;
								}
							}
						}
					}

					if (compressedResponse != null)
						context.Response.Write(compressedResponse);
					else
						context.Response.WriteFile(physicalPath);
					return;
				}
				#endregion

				string format = uri["format"];
				if (format == null)
					format = context.Request["format"];

				string physicalPathXml = physicalPath + ".xml";
				switch (baseExtension)
				{
					case ".js":
						physicalPathXml = physicalPath.Substring(0, physicalPath.Length - baseExtension.Length) + ".html.xml";
						format = "js";
						break;
					case ".css":
						physicalPathXml = physicalPath.Substring(0, physicalPath.Length - baseExtension.Length) + ".html.xml";
						format = "css";
						break;
				}
				if (ProcessDynamicRequest(physicalPathXml, format, uri, uriAccess, context, renderMode))
					return;
				if (ProcessDynamicRequest(context.Server.MapPath(UriManager.NotFoundUriString + ".xml"), format, uri, uriAccess, context, renderMode))
					return;
				ProcessNotFoundRequest(context, renderMode);
			}
			catch (ThreadAbortException tae)
			{
				Log.Fine(tae.Message + " Status: '{0}'.", context.Response.Status);
			}
			catch (Exception e)
			{
				Log.Throwing(Level.Warning, GetType().FullName, "ProcessRequest", e);
#if DEBUG
				throw new Exception("Exception catched.", e);
#else 
				ProcessErrorRequest(context, RenderMode.Production);
#endif
			}
		}

		private void ProcessStatusRequest(string message, System.Web.HttpContext context, RenderMode mode)
		{
			context.Response.ContentType = HtmlHelper.MimeType;
			context.Response.Write(HtmlHelper.Html5Doctype);
			context.Response.Write("<html><head><title>" + context.Response.Status + "</title></head><body><h1>" + context.Response.Status + "</h1><p>" + message + "</p></body></html>");
		}

		private void ProcessErrorRequest(System.Web.HttpContext context, RenderMode mode)
		{
			Log.Warning("Request error : '{0}'", context.Request.Url);
			context.Response.Clear();
			context.Response.Status = "500 Internal Error";
			context.Response.StatusCode = 500;
			context.Response.StatusDescription = "Internal Error";
			ProcessStatusRequest("The server encountered an unexpected condition which prevented it from fulfilling the request.", context, mode);
		}

		private void ProcessNotFoundRequest(System.Web.HttpContext context, RenderMode mode)
		{
			Log.Warning("Request unknown : '{0}'", context.Request.Url);
			context.Response.Status = "404 Not Found";
			context.Response.StatusCode = 404;
			context.Response.StatusDescription = "Not Found";
			ProcessStatusRequest("The resource you requested could not be found on our server.", context, mode);
		}

		private static void ObfuscateIdsInternal(Dictionary<string, HtmlElement> ids, HtmlElement element)
		{
			if (element.ServerSafeId)
			{
				FixIdsInternal(ids, element);
				return;
			}
			element.Id = "j" + ids.Count;
			ids.Add(element.Id, element);
		}
		private static void FixIdsInternal(Dictionary<string, HtmlElement> ids, HtmlElement element)
		{
			string postFix = string.Empty;
			int count = 1;
			while (ids.ContainsKey(element.Id + postFix))
			{
				count++;
				postFix = count.ToString();
			}
			element.Id = element.Id + postFix;
			ids.Add(element.Id, element);
		}
		public static void ObfuscateIds(Dictionary<string, HtmlElement> ids, HtmlElement element)
		{
			if (element.Id != null)
				ObfuscateIdsInternal(ids, element);
			if (element.AllowChildControls && element.ChildControls.Count > 0)
				foreach (HtmlControl subElement in element.ChildControls)
					if (subElement is HtmlElement)
						ObfuscateIds(ids, subElement as HtmlElement);
		}
		public static void FixIds(Dictionary<string, HtmlElement> ids, HtmlElement element)
		{
			if (element.Id != null)
				FixIdsInternal(ids, element);
			if (element.AllowChildControls && element.ChildControls.Count > 0)
				foreach (HtmlControl subElement in element.ChildControls)
					if (subElement is HtmlElement)
						FixIds(ids, subElement as HtmlElement);
		}

		private string DetectUserLang(params string[] languages)
		{
			if (languages == null)
				return Configuration.GetValue("Languages/@default", "en");
			HashSet<string> langs = new HashSet<string>(Configuration.GetValues("Languages/Lang/@lang"));
			string pattern = @"((?<lang>..(\-..)?)|\*)(;q=[^ ;]+)?";
			Regex regex = new Regex(pattern);
			for (int c = 0; c < languages.Length; c++)
			{
				Match match = regex.Match(languages[c]);
				if (match.Success)
				{
					if (langs.Contains(match.Groups["lang"].Value))
						return match.Groups["lang"].Value;
				}
			}
			return Configuration.GetValue("Languages/@default", "en");
		}

		private bool ProcessDynamicRequest(string physicalPathXml, string format, Uri uri, UriAccess uriAccess, System.Web.HttpContext context, RenderMode mode)
		{
			if (File.Exists(physicalPathXml))
			{
				HttpHandlerContext handlerContext = null;
				try 
				{
					Log.Fine("Request Dynamic File : '{0}'{1}.", physicalPathXml, context.User.Identity.IsAuthenticated ? string.Format(" for username '{0}'", context.User.Identity.Name) : string.Empty);
					Html html = Html.LoadControl(physicalPathXml, ControlLoader) as Html;
					HttpEventHandlerManager handlerManager = new HttpEventHandlerManager();
#region Detect Translator
					ITranslator translator = null;
					string defaultLanguage = DetectUserLang(context.Request.UserLanguages);
					string userLang = (uri["lang"] ?? context.Request["lang"] ?? context.Session["lang"] ?? defaultLanguage).ToString();
					userLang = DetectUserLang(userLang);
					context.Session["lang"] = userLang;
					CultureInfo cultureInfo = new CultureInfo(userLang);
					Thread.CurrentThread.CurrentCulture = cultureInfo;
					Thread.CurrentThread.CurrentUICulture = cultureInfo;
					string translationPath = context.Server.MapPath("/" + userLang + ".xml");
					if (File.Exists(translationPath))
					{
						XmlTranslator xmlTranslator = new XmlTranslator(userLang);
						xmlTranslator.Load(translationPath);
						translator = xmlTranslator;
					}
					if (translator == null)
					{
						XmlTranslator xmlTranslator = new XmlTranslator(userLang);
						translator = xmlTranslator;
					}
#endregion

					if (html != null)
					{
						Dictionary<string, HtmlElement> ids = new Dictionary<string, HtmlElement>();
						if (mode == RenderMode.Production)
							ObfuscateIds(ids, html);
						else
							FixIds(ids, html);

						if (string.IsNullOrWhiteSpace(format))
							format = "html";
						switch (format)
						{
							case "html":
								handlerContext = new HttpHandlerContext(this, Configuration, Log, uri, uriAccess, HttpRequestType.Html, context, mode, translator, html, null, null, null, handlerManager, ControlLoader);
								ProcessHtmlRequest(handlerContext);
								break;
							case "json":
								handlerContext = new HttpHandlerContext(this, Configuration, Log, uri, uriAccess, HttpRequestType.Json, context, mode, translator, html, new JsonObject(), null, null, handlerManager, ControlLoader);
								ProcessJsonRequest(handlerContext);
								break;
							case "js":
								handlerContext = new HttpHandlerContext(this, Configuration, Log, uri, uriAccess, HttpRequestType.Js, context, mode, translator, html, null, new JsFile(), null, handlerManager, ControlLoader);
								ProcessJsRequest(handlerContext);
								break;
							case "css":
								handlerContext = new HttpHandlerContext(this, Configuration, Log, uri, uriAccess, HttpRequestType.Css, context, mode, translator, html, null, null, new CssFile(), handlerManager, ControlLoader);
								ProcessCssRequest(handlerContext);
								break;
							case "bin":
								handlerContext = new HttpHandlerContext(this, Configuration, Log, uri, uriAccess, HttpRequestType.Bin, context, mode, translator, html, null, null, null, handlerManager, ControlLoader);
								ProcessBinRequest(handlerContext);
								break;
							default:
								return false;
						}
					}
#if DEBUG
					if (translator is XmlTranslator)
					{
						if (context.Request["save-translation"] == "true")
						{
							XmlTranslator xmlTranslator = translator as XmlTranslator;
							xmlTranslator.Save(translationPath);
						}
					}
#endif
				}
				finally
				{
					if (handlerContext != null)
						handlerContext.Dispose();
				}
				return true;
			}
			return false;
		}

		private void ProcessBinRequest(HttpHandlerContext context)
		{
			context.Html.ProcessBinRequest(context);
		}

		private static void AddQueryString<T>(HttpHandlerContext context, string attributeName, Jayden.Dll.Web.Uri currentUri, string queryName, string queryValue) where T:HtmlElement
		{
			foreach (T a in context.Html.GetElements<T>(delegate(HtmlElement control) { return control[attributeName] != null; }, null))
			{
				Jayden.Dll.Web.Uri uri = new Web.Uri(context.Context.Request.Url, a[attributeName], true);
				if (uri.Host == currentUri.Host && uri.Port == currentUri.Port && uri.Protocol == currentUri.Protocol)
				{
					uri[queryName] = queryValue;
					// html encoding is done in the set of the attribute
					a[attributeName] = uri.ToAbsoluteString(true);
				}
			}
		}

		private void ProcessHtmlRequest(HttpHandlerContext context)
		{
			Log.Fine("Start Processing Html request for user-agent '{0}'", context.UserAgent);
			// comments between elements are used in debug mode to avoid blanks altering the rendering compared to production mode
			context.Context.Response.ContentType = HtmlHelper.MimeType;
			context.Context.Response.Write(HtmlHelper.Html5Doctype);
			if (context.RenderMode == RenderMode.Debug)
				context.Context.Response.Write(string.Format("<!-- Html5: Generated for {0} at {1} (user-agent:'{2}')", context.Context.Request.Url, DateTime.Now.ToString(CultureInfo.InvariantCulture), context.UserAgent));
			context.Html.ProcessHtmlRequest(context);
			context.HandlerManager.ProcessHtmlRequest(context);
			context.Html.TranslateControl(context.Translator);
			// those attributes are (by convention) server-sided or irrelevant for client side so we remove them before rendering
			context.Html.CleanAttributes(new Regex("data\\-server\\-.*"), true);
			context.Html.CleanAttributes(new Regex("data\\-clean\\-.*"), true);
			#region Forward RenderMode
			string renderModeString = context.RenderMode.ToString().ToLowerInvariant();
			RenderMode defaultMode = RenderMode.Production;
#if DEBUG
			defaultMode = RenderMode.Debug;
#endif
			if (context.RenderMode != defaultMode)
			{
				Jayden.Dll.Web.Uri currentUri = new Jayden.Dll.Web.Uri(context.Context.Request.Url);
				AddQueryString<A>(context, "href", currentUri, "renderMode", renderModeString);
				AddQueryString<Link>(context, "href", currentUri, "renderMode", renderModeString);
				AddQueryString<Script>(context, "src", currentUri, "renderMode", renderModeString);
				AddQueryString<IFrame>(context, "src", currentUri, "renderMode", renderModeString);
				AddQueryString<Form>(context, "action", currentUri, "renderMode", renderModeString);
			}
			if (context.Request["degradeJs"] == "true")
			{
				Jayden.Dll.Web.Uri currentUri = new Jayden.Dll.Web.Uri(context.Context.Request.Url);
				AddQueryString<Script>(context, "src", currentUri, "degradeJs", "true");
				AddQueryString<A>(context, "href", currentUri, "degradeJs", "true");
				AddQueryString<IFrame>(context, "src", currentUri, "degradeJs", "true");
				AddQueryString<Form>(context, "action", currentUri, "degradeJs", "true");
			}
			if (context.Request["degradeCss"] == "true")
			{
				Jayden.Dll.Web.Uri currentUri = new Jayden.Dll.Web.Uri(context.Context.Request.Url);
				AddQueryString<A>(context, "href", currentUri, "degradeCss", "true");
				AddQueryString<Link>(context, "href", currentUri, "degradeCss", "true");
				AddQueryString<IFrame>(context, "src", currentUri, "degradeCss", "true");
				AddQueryString<Form>(context, "action", currentUri, "degradeCss", "true");
			}
			#endregion

			// ensure the generated code will be html5 compliant
			if (context.RenderMode == RenderMode.Debug)
				HtmlDescription.Instance.CheckValidity(context.Html);

			// start rendering
			using (TextWriter writer = new StreamWriter(context.Context.Response.OutputStream))
			{
				HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
				if (context.RenderMode == RenderMode.Debug || context.RenderMode == RenderMode.XHtml)
					htmlWriter.WriteLine();
				context.Html.Render(htmlWriter, context.RenderMode, 0);
				if (context.RenderMode == RenderMode.Debug)
					htmlWriter.Write("EOF -->");
				htmlWriter.Flush();
			}
		}

		private void ProcessJsonRequest(HttpHandlerContext context)
		{
			context.Context.Response.ContentType = JsonHelper.MimeType;
			context.Html.ProcessJsonRequest(context);
			context.HandlerManager.ProcessJsonRequest(context);
			context.Context.Response.Write(context.Json.ToJson(context.RenderMode, 0));
		}

		private void ProcessJsRequest(HttpHandlerContext context)
		{
			context.Context.Response.ContentType = JsHelper.MimeType;
			context.Html.ProcessJsRequest(context);
			context.HandlerManager.ProcessJsRequest(context);
			string jsScript = string.Empty;

			if (context.Request["degradeJs"] == "true")
				jsScript = "/* degradeJs is true no JavaScript file will be available. */";
			else
			{
				using (TextWriter writer = new StringWriter())
				{
					writer.WriteLine("\"use strict\";");
					if (context.RenderMode == RenderMode.Debug)
						writer.WriteLine(string.Format("/* Es5: Generated for {0} at {1} */", context.Context.Request.Url, DateTime.Now.ToString(CultureInfo.InvariantCulture)));
					context.Js.Render(writer, context.RenderMode);
					jsScript = writer.ToString();
				}
				if (context.RenderMode == RenderMode.Production)
					jsScript = JavaScriptCompressor.Compress(jsScript);
			}
			context.Context.Response.Write(jsScript);
		}

		private void ProcessCssRequest(HttpHandlerContext context)
		{
			context.Context.Response.ContentType = CssFile.MimeType;
			context.Html.ProcessCssRequest(context);
			context.HandlerManager.ProcessCssRequest(context);
			string cssScript = string.Empty;
			if (context.Request["degradeCss"] == "true")
				cssScript = "/* degradeCss is true no Css file will be available. */";
			else
			{
				string version = string.Empty;
				if (context.RenderMode == RenderMode.Debug)
				{
					int versionCode = context.Css.CheckValidity();
					version = versionCode.ToString();
				}
				using (TextWriter writer = new StringWriter())
				{
					if (context.RenderMode == RenderMode.Debug)
						writer.WriteLine("/* Css{2}: Generated for {0} at {1} */", context.Context.Request.Url, DateTime.Now.ToString(CultureInfo.InvariantCulture), version);
					context.Css.Render(writer, context.RenderMode, 0);
					cssScript = writer.ToString();
				}
				if (context.RenderMode == RenderMode.Production)
					cssScript = CssCompressor.Compress(cssScript);
			}
			context.Context.Response.Write(cssScript);
		}

	}
}
